extends Node2D
class_name BattleField

@onready var stateMachine: StateMachine = $StateMachine
@onready var heroRoot: Node2D = $Heros
@onready var cursor: Cursor = $Cursor
@onready var cursorMoveCooldownTimer: Timer = $CursorMoveCooldownTimer
@onready var cursorMoveContiniousDelayTimer: Timer = $CursorMoveContiniousDelayTimer
@onready var cam: BattleFieldCamera = $Camera2D
@onready var battleAnimScene: BattleAnimScene = $BattleAnimScene

# UI
@onready var battleStatusPanel: Control = $UI/BattleStatusPanel
@onready var battleCommandPanel: BattleCommandPanel = $UI/BattleCommandPanel

var config
var heroBornPoints:Array
var mapParent:Node2D
var tileMap:TileMap
var tileMapWidth
var tileMapHeight
var terrainCells:Array

var heroDatasInFight:Array
var heroDatasGlobal:Array

var heroUnits:Array
var enemyUnits:Array

var currentSelectedHero:BattleFieldUnit = null
var currentSelectedHeroStartCell = null
var movableCells = []
var shortestPaths = {}
var selectableTargets:Array
var currentSelectTarget 
var currentSelectTargetIndex = 0
var currentSelectedOption = ""


func _exit_tree() -> void:
	battleCommandPanel.onSelectResult.disconnect(onSelectCommand)


func init(configId, _heroDatasGlobal):
	config = ConfigClasses.BattleConfig.idConfigDict[configId]
	
	initMap()
	initCam()
	initHeros(_heroDatasGlobal)
	initEnemies()
	initCursor()
	listenBattleCommand()
	
	if heroUnits.size() > 0:
		setCamFocusTarget(heroUnits[0].getCell())
	
	stateMachine.addState(Consts.STATE_BATTLEFIELD_FREESELECT)
	
	
func listenBattleCommand():
	battleCommandPanel.onSelectResult.connect(onSelectCommand)
	

func onSelectCommand(index, option):
	currentSelectedOption = option
	if option == Consts.OPTION_ATTACK:
		stateMachine.clearAndAddState(Consts.STATE_BATTLEFIELD_SELECTTARGET)
	battleCommandPanel.hide()
		
		
func processSelectTarget():
	if Input.is_action_just_pressed("MoveLeft") or Input.is_action_just_pressed("MoveUp") or Input.is_action_just_pressed("MoveRight") or Input.is_action_just_pressed("MoveDown"):
		currentSelectTargetIndex += 1
		if currentSelectTargetIndex >= selectableTargets.size():
			currentSelectTargetIndex = 0
		
		cursor.setCell(selectableTargets[currentSelectTargetIndex].getCell())


func processConfirmSelectTarget():
	if Input.is_action_just_pressed("Confirm"):
		startBattleAnim()
		stateMachine.clearAndAddState(Consts.STATE_BATTLEFIELD_BATTLEANIM)


func initMap():
	mapParent = GlobalUtils.createResourceInstance(config.mapId)
	add_child(mapParent)
	tileMap = mapParent.find_child("TileMap")
	tileMapWidth = tileMap.get_used_rect().size.x
	tileMapHeight = tileMap.get_used_rect().size.y
	terrainCells = mapParent.find_child("TerrainCells").get_children()
	
	heroBornPoints = mapParent.find_child("HeroBornPoints").get_children()
	for point in heroBornPoints:
		point.visible = false
		

func getTerrainInfoByCell(cell):
	var result = null
	for terrain in terrainCells:
		if terrain.getCell() == cell:
			result = terrain
	return result
		

func initCam():
	cam.setBorder(0, 0, tileMapWidth * Consts.BATTLE_CELL_SIZE, tileMapHeight * Consts.BATTLE_CELL_SIZE)
		

func initHeros(_heroDatasGlobal):
	heroDatasGlobal = _heroDatasGlobal
	heroDatasInFight = []
	for hero in _heroDatasGlobal:
		hero.battleHp = hero.hp
		heroDatasInFight.append(hero) 
	
	for index in range(heroDatasInFight.size()):
		if index >= heroBornPoints.size():
			break
		
		var unit = GlobalUtils.createResourceInstance(Consts.BATTLE_FIELD_UNIT)
		heroRoot.add_child(unit)
		unit.initByCharData(heroDatasInFight[index])
		unit.setCell(heroBornPoints[index].getCell())
		unit.setIsEnemy(false)
		heroUnits.append(unit)
		
		
func initEnemies():
	enemyUnits = mapParent.find_child("Enemies").get_children()
	for unit in enemyUnits:
		unit.setIsEnemy(true)
	

func _physics_process(delta: float) -> void:
	stateMachine.run(delta)
	

func setCursorVisible(b):
	cursor.visible = b
	

func setCursorCell(cell):
	cursor.setCell(cell)
	
	
func processCursorMove():
	if Input.is_action_just_pressed("MoveLeft"):
		cursorMoveContiniousDelayTimer.start()
		moveCursor(Vector2.LEFT)
		
	if Input.is_action_just_pressed("MoveUp"):
		cursorMoveContiniousDelayTimer.start()
		moveCursor(Vector2.UP)
		
	if Input.is_action_just_pressed("MoveRight"):
		cursorMoveContiniousDelayTimer.start()
		moveCursor(Vector2.RIGHT)
		
	if Input.is_action_just_pressed("MoveDown"):
		cursorMoveContiniousDelayTimer.start()
		moveCursor(Vector2.DOWN)
		
	if cursorMoveContiniousDelayTimer.time_left <= 0 and cursorMoveCooldownTimer.time_left <= 0:
		if Input.is_action_pressed("MoveLeft"):
			moveCursor(Vector2.LEFT)
			cursorMoveCooldownTimer.start()
		if Input.is_action_pressed("MoveUp") :
			moveCursor(Vector2.UP)
			cursorMoveCooldownTimer.start()
		if Input.is_action_pressed("MoveRight") :
			moveCursor(Vector2.RIGHT)
			cursorMoveCooldownTimer.start()
		if Input.is_action_pressed("MoveDown") :
			moveCursor(Vector2.DOWN)
			cursorMoveCooldownTimer.start()
			

func moveCursorInMovableCell(direction):
	var targetCell = cursor.getCell() + direction
	var canMove = false
	for cell in movableCells:
		if cell == targetCell:
			canMove = true
			break
	
	if not canMove: return
	moveCursor(direction)
	

func moveCursor(direction):
	setCursorCell(trimCellByBorder(cursor.getCell() + direction))
	cursor.restartFromVisible()
	
	if direction == Vector2.LEFT:
		var nowCursorPosLeft = GlobalUtils.cell2PosLeft(cursor.getCell()) 
		if cam.isPosBeyondView(nowCursorPosLeft):
			cam.moveLeft(Consts.BATTLE_CELL_SIZE)

	if direction == Vector2.UP:
		var nowCursorPosUp = GlobalUtils.cell2PosUp(cursor.getCell()) 
		if cam.isPosBeyondView(nowCursorPosUp):
			cam.moveUp(Consts.BATTLE_CELL_SIZE)
			
	if direction == Vector2.RIGHT:
		var nowCursorPosRight = GlobalUtils.cell2PosRight(cursor.getCell()) 
		if cam.isPosBeyondView(nowCursorPosRight):
			cam.moveRight(Consts.BATTLE_CELL_SIZE)
			
	if direction == Vector2.DOWN:
		var nowCursorPosDown = GlobalUtils.cell2PosDown(cursor.getCell()) 
		if cam.isPosBeyondView(nowCursorPosDown):
			cam.moveDown(Consts.BATTLE_CELL_SIZE)


func initCursor():
	setCursorVisible(true)
	setCursorCell(heroUnits[0].getCell())
	

func trimCellByBorder(cell):
	if cell.x < 0:
		cell.x = 0
	if cell.y < 0:
		cell.y = 0
	if cell.x >= tileMapWidth:
		cell.x = tileMapWidth - 1
	if cell.y >= tileMapHeight:
		cell.y = tileMapHeight - 1

	return cell
	

func processSelect():
	if not Input.is_action_just_pressed("Confirm"): return
	var unit = getUnitByCell(cursor.getCell())
	if not unit: return
	
	if not unit.isEnemy:
		# select hero
		currentSelectedHero = unit
		currentSelectedHeroStartCell = currentSelectedHero.getCell()
		currentSelectedHero.setReady()
		currentSelectedHero.animatedSprite2D.play("moveDown")
		calcMoveableCells(currentSelectedHero)
		stateMachine.clearAndAddState(Consts.STATE_BATTLEFIELD_STATUS)
		

func getUnitByCell(cell):
	var target = null
	
	for unit in heroUnits:
		var unitCell = unit.getCell()
		if unitCell.x == cell.x and unitCell.y == cell.y:
			target = unit
			break; 
			
	for unit in enemyUnits:
		var unitCell = unit.getCell()
		if unitCell.x == cell.x and unitCell.y == cell.y:
			target = unit
			break; 
			
	return target
	
	
func calcMoveableCells(unit):
	var blockCells:Array = []
	for cell in terrainCells:
		if cell.block:
			blockCells.append(cell.getCell())
			
	for enemyUnit in enemyUnits:
		blockCells.append(enemyUnit.getCell())
	
	var specialCostCells:Dictionary = {}
	for cell in terrainCells:
		if cell.moveCost > 1:
			specialCostCells[cell.getCell()] = cell.moveCost
			
	var border = Rect2(0, 0, tileMapWidth, tileMapHeight)
	
	var result = GlobalUtils.calcMovableCells(unit.getCell(), unit.charData.move, blockCells, specialCostCells, border)
	movableCells = result["movableCells"]
	shortestPaths = result["shortestPaths"]
	
	return result
	
	
func processCursorMoveReady():
	if Input.is_action_just_pressed("MoveLeft"):
		cursorMoveContiniousDelayTimer.start()
		moveCursorInMovableCell(Vector2.LEFT)
		
	if Input.is_action_just_pressed("MoveUp"):
		cursorMoveContiniousDelayTimer.start()
		moveCursorInMovableCell(Vector2.UP)
		
	if Input.is_action_just_pressed("MoveRight"):
		cursorMoveContiniousDelayTimer.start()
		moveCursorInMovableCell(Vector2.RIGHT)
		
	if Input.is_action_just_pressed("MoveDown"):
		cursorMoveContiniousDelayTimer.start()
		moveCursorInMovableCell(Vector2.DOWN)
		
	if cursorMoveContiniousDelayTimer.time_left <= 0 and cursorMoveCooldownTimer.time_left <= 0:
		if Input.is_action_pressed("MoveLeft"):
			moveCursorInMovableCell(Vector2.LEFT)
			cursorMoveCooldownTimer.start()
		if Input.is_action_pressed("MoveUp") :
			moveCursorInMovableCell(Vector2.UP)
			cursorMoveCooldownTimer.start()
		if Input.is_action_pressed("MoveRight") :
			moveCursorInMovableCell(Vector2.RIGHT)
			cursorMoveCooldownTimer.start()
		if Input.is_action_pressed("MoveDown") :
			moveCursorInMovableCell(Vector2.DOWN)
			cursorMoveCooldownTimer.start()


func processCancelSelect():
	if Input.is_action_just_pressed("Cancel"):
		stateMachine.clearAndAddState(Consts.STATE_BATTLEFIELD_FREESELECT)
		currentSelectedHero.setIdle()
		currentSelectedHero = null


func processConfirmMove():
	if Input.is_action_just_pressed("Confirm"):
		currentSelectedHero.movePath(shortestPaths[cursor.getCell()])
		currentSelectedHero.onMoveArriveTarget.connect(onUnitMoveArriveTarget)
		stateMachine.clearAndAddState(Consts.STATE_BATTLEFIELD_MOVE)
		
		
func onUnitMoveArriveTarget(unit):
	stateMachine.clearAndAddState(Consts.STATE_BATTLEFIELD_COMMAND)
	

func setCamFocusTarget(cell):
	cam.setFocus(GlobalUtils.cell2PosCenter(cell))


func showCurrentCharStatus():
	battleStatusPanel.show()
	battleStatusPanel.init(currentSelectedHero.charData)
	

func hideCurrentCharStatus():
	battleStatusPanel.hide()
	

func processConfirm2Ready():
	if not Input.is_action_just_pressed("MoveLeft") and not Input.is_action_just_pressed("MoveUp") and not Input.is_action_just_pressed("MoveRight") and not Input.is_action_just_pressed("MoveDown"):
		return
	cursor.restartFromVisible()
	cursorMoveContiniousDelayTimer.start()
	stateMachine.clearAndAddState(Consts.STATE_BATTLEFIELD_READY)


func processCancelReady():
	if Input.is_action_just_pressed("Cancel"):
		setCursor2CurrentSelectedChar()
		stateMachine.clearAndAddState(Consts.STATE_BATTLEFIELD_STATUS)
		

func setCursor2CurrentSelectedChar():
	if not currentSelectedHero: return
	cursor.setCell(currentSelectedHero.getCell())
	

func processStatusDirect2Command():
	if Input.is_action_just_pressed("Confirm"):
		stateMachine.clearAndAddState(Consts.STATE_BATTLEFIELD_COMMAND)
		

func processCancelCommand():
	if Input.is_action_just_pressed("Cancel"):
		if currentSelectedHero.getCell() == currentSelectedHeroStartCell:
			stateMachine.clearAndAddState(Consts.STATE_BATTLEFIELD_STATUS)
		else:
			stateMachine.clearAndAddState(Consts.STATE_BATTLEFIELD_READY)
	
		currentSelectedHero.setReady()
		currentSelectedHero.setCell(currentSelectedHeroStartCell)
		cursor.setCell(currentSelectedHeroStartCell)
		battleCommandPanel.hide()
		

func getUnitCommandOptions(unit:BattleFieldUnit):
	var result = ""
	
	var weaponType = ConfigClasses.WeaponTypeConfig.idConfigDict[unit.charData.careerConfig.weaponType]
	var canAttack = weaponType.canAttack
	
	if canAttack:
		var range = int(weaponType.range) 
		var enemiesInRange = getUnitsInRange(unit.getCell(), range, enemyUnits)
		selectableTargets = enemiesInRange
		if enemiesInRange.size() > 0:
			result += "攻击\n"
		
	result += "待命"
	
	return result
	
	
func getUnitsInRange(start, range, units):
	var unitsInRange = []
	for unit in units:
		var unitCell = unit.getCell()
		var distance = abs(unitCell.x - start.x) + abs(unitCell.y - start.y) 
		if distance <= range:
			unitsInRange.append(unit)
			
	return unitsInRange
	

func startBattleAnimAttack(attacker, receiver):
	# case 1 attack 
	var battleAnimData = CustomClasses.BattleAnimData.new()
	
	# cutscenecmd
	var cutSceneCmd = "enterCutScene,{dir}"
	
	if attacker.isEnemy:
		battleAnimData.unitLeft = attacker
		battleAnimData.unitRight = receiver
		cutSceneCmd = cutSceneCmd.format({
			"dir": "left"
		})
	else:
		battleAnimData.unitLeft = receiver
		battleAnimData.unitRight = attacker
		cutSceneCmd = cutSceneCmd.format({
			"dir": "right"
		})
	
	# attack data
	battleAnimData.attackDataLeft = battleAnimData.unitLeft.charData.attackData(battleAnimData.unitRight.charData)
	battleAnimData.attackDataRight = battleAnimData.unitRight.charData.attackData(battleAnimData.unitLeft.charData)
		
	battleAnimData.sequences.append(cutSceneCmd)
	
	var attackResult:Dictionary = attacker.charData.simulateAttackResult(receiver.charData)
	
	var firstAttackCmd = "attack,{dir},{round},{attackType},{damage}"
	var dir = "right"
	if attacker.isEnemy: dir = "left"
	var round = "first"
	var attackType = "attack"
	if attackResult["firstCritical"]:
		attackType = "critical" 
	var damage = attackResult["firstActualDamage"]
	firstAttackCmd = firstAttackCmd.format({
		"dir": dir,
		"round": round,
		"attackType": attackType,
		"damage": damage
	})
	battleAnimData.sequences.append(firstAttackCmd)
	
	if attackResult.has("fanjiActualDamage"):
		var fanjiAttackCmd = "attack,{dir},{round},{attackType},{damage}"
		dir = "right"
		if receiver.isEnemy: dir = "left"
		round = "fanji"
		attackType = "attack"
		if attackResult["fanjiCritical"]:
			attackType = "critical" 
		damage = attackResult["fanjiActualDamage"]
		fanjiAttackCmd = fanjiAttackCmd.format({
			"dir": dir,
			"round": round,
			"attackType": attackType,
			"damage": damage
		})
		battleAnimData.sequences.append(fanjiAttackCmd)
	
	if attackResult.has("zhuijiActualDamage"):
		var zhuijiAttackCmd = "attack,{dir},{round},{attackType},{damage}"
		dir = "right"
		if attacker.isEnemy: dir = "left"
		round = "zhuiji"
		attackType = "attack"
		if attackResult["zhuijiCritical"]:
			attackType = "critical" 
		damage = attackResult["zhuijiActualDamage"]
		zhuijiAttackCmd = zhuijiAttackCmd.format({
			"dir": dir,
			"round": round,
			"attackType": attackType,
			"damage": damage
		})
		battleAnimData.sequences.append(zhuijiAttackCmd)
	
	battleAnimData.sequences.append_array([
		"wait",
		"summary",
		"wait",
		"exp"
	])
	
	battleAnimScene.show()
	battleAnimScene.init(battleAnimData, self)
	

func startBattleAnim():
	# case 1 attack
	startBattleAnimAttack(currentSelectedHero, currentSelectTarget)
	

func processOpenCommandMenu():
	if not Input.is_action_just_pressed("Confirm"): return
	
	var unit = getUnitByCell(cursor.getCell())
	if unit: return
	
	stateMachine.clearAndAddState(Consts.STATE_BATTLEFIELD_COMMANDMENU)


func openCommandMenu():
	var options = "测试\n测试\n回合结束"
	battleCommandPanel.setOption(options)
	battleCommandPanel.showPanel()
	
	
func processCancelCommandMenu():
	if Input.is_action_just_pressed("Cancel"):
		stateMachine.clearAndAddState(Consts.STATE_BATTLEFIELD_FREESELECT)
